Coroutines, introduced in C++20, are functions in which execution can be suspended and resumed. When a coroutine resumes, it takes over where it
left thanks to the coroutine state.
A coroutine state is an object which contains all the information a coroutine needs to resume its execution correctly: local variables,
copy of the parameters…
This means that if a coroutine has a parameter that is a reference to an object, this object must exist as long as the coroutine is not destroyed.
Otherwise, the reference stored in the coroutine state will become a dangling reference and will lead to undefined behavior when the
coroutine resumes.
The issue is raised for all coroutine parameters with reference-to-const semantics (such as a const
reference, a
std::string_view
, or a std::span
with const
elements) that might be used after the coroutine is suspended.
To fix the issue, you can either pass the parameter by value, or not use the parameter after the first suspension point (co_await
,
co_yield
, or initial_suspend
).
Noncompliant code example
generator<char> spell(const std::string& m) { // Noncompliant
for (char letter : m) {
co_yield letter;
}
}
void print() {
for (char letter : spell("hello world")) { // Here the parameter "m" binds to a temporary
std::cout << letter << '\n'; // and becomes dangling on the next iteration
}
}
Compliant solution
generator<char> spell(const std::string m) { // Compliant: take the argument by copy
for (char letter : m) {
co_yield letter;
}
}
void print() {
for (char letter : spell("hello world")) {
std::cout << letter << '\n';
}
}
Exceptions
This rule does not raise an issue for std::reference_wrapper
parameters taking it as a witness of the care taken to prevent the
reference to become dangling.